import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QPushButton, QSlider, QFileDialog, QMessageBox,
                             QTabWidget, QGroupBox, QGridLayout, QComboBox, QSpinBox,
                             QDoubleSpinBox, QRadioButton, QButtonGroup, QProgressBar)
from PyQt5.QtGui import QImage, QPixmap, QPainter, QColor, QPen
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QPoint, QTimer
import os
import time


class ImageProcessor:
    """图像处理核心类，负责所有图像处理算法实现"""

    def __init__(self):
        self.original_img = None  # 原始图像
        self.current_img = None  # 当前处理后的图像
        self.history = []  # 操作历史
        self.history_index = -1  # 当前历史位置

    def load_image(self, path):
        """加载图像"""
        self.original_img = cv2.imread(path)
        if self.original_img is None:
            raise ValueError("无法加载图像，请检查文件路径和格式")
        self.original_img = cv2.cvtColor(self.original_img, cv2.COLOR_BGR2RGB)
        self.current_img = self.original_img.copy()
        self.reset_history()
        return self.current_img

    def reset_history(self):
        """重置历史记录"""
        self.history = [self.original_img.copy()]
        self.history_index = 0

    def save_history(self):
        """保存当前状态到历史记录"""
        # 如果不是在历史末尾，删除后面的记录
        if self.history_index < len(self.history) - 1:
            self.history = self.history[:self.history_index + 1]
        self.history.append(self.current_img.copy())
        self.history_index += 1

    def undo(self):
        """撤销操作"""
        if self.history_index > 0:
            self.history_index -= 1
            self.current_img = self.history[self.history_index].copy()
            return self.current_img
        return None

    def redo(self):
        """重做操作"""
        if self.history_index < len(self.history) - 1:
            self.history_index += 1
            self.current_img = self.history[self.history_index].copy()
            return self.current_img
        return None

    def rotate(self, angle):
        """旋转图像"""
        self.save_history()
        rows, cols = self.current_img.shape[:2]
        M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1)
        self.current_img = cv2.warpAffine(self.current_img, M, (cols, rows))
        return self.current_img

    def resize(self, scale):
        """缩放图像"""
        self.save_history()
        width = int(self.current_img.shape[1] * scale)
        height = int(self.current_img.shape[0] * scale)
        self.current_img = cv2.resize(self.current_img, (width, height), interpolation=cv2.INTER_AREA)
        return self.current_img

    def crop(self, x1, y1, x2, y2):
        """裁剪图像"""
        self.save_history()
        self.current_img = self.current_img[y1:y2, x1:x2]
        return self.current_img

    def adjust_brightness(self, value):
        """调整亮度"""
        self.save_history()
        hsv = cv2.cvtColor(self.current_img, cv2.COLOR_RGB2HSV)
        hsv[..., 2] = np.clip(hsv[..., 2] + value, 0, 255)
        self.current_img = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
        return self.current_img

    def adjust_contrast(self, value):
        """调整对比度"""
        self.save_history()
        alpha = value / 100.0 + 1
        self.current_img = np.clip(self.current_img * alpha, 0, 255).astype(np.uint8)
        return self.current_img

    def adjust_saturation(self, value):
        """调整饱和度"""
        self.save_history()
        hsv = cv2.cvtColor(self.current_img, cv2.COLOR_RGB2HSV)
        hsv[..., 1] = np.clip(hsv[..., 1] * (value / 100 + 1), 0, 255)
        self.current_img = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
        return self.current_img

    def smooth(self, method, kernel_size=3):
        """平滑处理"""
        self.save_history()
        if method == "均值滤波":
            self.current_img = cv2.blur(self.current_img, (kernel_size, kernel_size))
        elif method == "高斯滤波":
            self.current_img = cv2.GaussianBlur(self.current_img, (kernel_size, kernel_size), 0)
        elif method == "中值滤波":
            self.current_img = cv2.medianBlur(self.current_img, kernel_size)
        return self.current_img

    def detect_shapes(self):
        """形状识别"""
        self.save_history()
        img_copy = self.current_img.copy()
        gray = cv2.cvtColor(img_copy, cv2.COLOR_RGB2GRAY)
        _, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)
        contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        for cnt in contours:
            perimeter = cv2.arcLength(cnt, True)
            approx = cv2.approxPolyDP(cnt, 0.02 * perimeter, True)

            if len(approx) == 3:
                shape = "三角形"
            elif len(approx) == 4:
                shape = "矩形"
            elif 5 <= len(approx) <= 10:
                shape = "多边形"
            else:
                shape = "圆形"

            cv2.drawContours(img_copy, [cnt], -1, (0, 255, 0), 2)
            cv2.putText(img_copy, shape, (approx[0][0][0], approx[0][0][1] - 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

        self.current_img = img_copy
        return self.current_img

    def detect_faces(self):
        """人脸检测"""
        self.save_history()
        img_copy = self.current_img.copy()
        gray = cv2.cvtColor(img_copy, cv2.COLOR_RGB2GRAY)

        # 加载预训练模型
        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')

        faces = face_cascade.detectMultiScale(gray, 1.1, 4)
        for (x, y, w, h) in faces:
            cv2.rectangle(img_copy, (x, y), (x + w, y + h), (255, 0, 0), 2)

            # 检测眼睛
            roi_gray = gray[y:y + h, x:x + w]
            roi_color = img_copy[y:y + h, x:x + w]
            eyes = eye_cascade.detectMultiScale(roi_gray)
            for (ex, ey, ew, eh) in eyes:
                cv2.rectangle(roi_color, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 2)

        self.current_img = img_copy
        return self.current_img

    def stitch_images(self, images):
        """图像拼接"""
        stitcher = cv2.Stitcher_create()
        status, result = stitcher.stitch(images)

        if status == cv2.Stitcher_OK:
            self.current_img = result
            self.reset_history()
            return self.current_img
        else:
            raise RuntimeError(f"图像拼接失败，错误码: {status}")


class VideoProcessor(QThread):
    """视频处理线程，用于实时视频流处理"""

    frame_updated = pyqtSignal(np.ndarray)
    process_finished = pyqtSignal()

    def __init__(self, camera_id=0, processing_function=None):
        super().__init__()
        self.camera_id = camera_id
        self.processing_function = processing_function
        self.running = False
        self.cap = None

    def run(self):
        """线程运行函数"""
        self.running = True
        self.cap = cv2.VideoCapture(self.camera_id)

        if not self.cap.isOpened():
            self.process_finished.emit()
            return

        while self.running:
            ret, frame = self.cap.read()
            if not ret:
                break

            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

            # 应用处理函数
            if self.processing_function:
                processed_frame = self.processing_function(frame)
                self.frame_updated.emit(processed_frame)
            else:
                self.frame_updated.emit(frame)

            # 控制帧率
            self.msleep(30)

        self.cap.release()
        self.process_finished.emit()

    def stop(self):
        """停止线程"""
        self.running = False
        self.wait()


class VideoFileProcessor(QThread):
    """视频文件处理线程，用于播放本地视频文件"""

    frame_updated = pyqtSignal(np.ndarray, int, int)  # 发送帧、当前帧号、总帧数
    process_finished = pyqtSignal()

    def __init__(self, file_path=None, processing_function=None):
        super().__init__()
        self.file_path = file_path
        self.processing_function = processing_function
        self.running = False
        self.paused = False
        self.stopped = False
        self.current_frame = 0
        self.total_frames = 0
        self.fps = 0
        self.cap = None

    def run(self):
        """线程运行函数"""
        self.running = True
        self.paused = False
        self.stopped = False

        if not self.file_path:
            self.process_finished.emit()
            return

        self.cap = cv2.VideoCapture(self.file_path)

        if not self.cap.isOpened():
            self.process_finished.emit()
            return

        self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.fps = self.cap.get(cv2.CAP_PROP_FPS)
        self.current_frame = 0

        while self.running:
            if self.stopped:
                break

            if self.paused:
                self.msleep(100)
                continue

            ret, frame = self.cap.read()
            if not ret:
                break

            self.current_frame = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))

            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

            # 应用处理函数
            if self.processing_function:
                processed_frame = self.processing_function(frame)
                self.frame_updated.emit(processed_frame, self.current_frame, self.total_frames)
            else:
                self.frame_updated.emit(frame, self.current_frame, self.total_frames)

            # 控制帧率
            delay = int(1000 / self.fps)
            self.msleep(delay)

        self.cap.release()
        self.process_finished.emit()

    def stop(self):
        """停止播放"""
        self.stopped = True
        self.running = False
        self.wait()

    def pause(self):
        """暂停播放"""
        self.paused = True

    def resume(self):
        """恢复播放"""
        self.paused = False

    def seek(self, frame_pos):
        """跳转到指定帧"""
        if self.cap and self.cap.isOpened():
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_pos)
            self.current_frame = frame_pos


class MainWindow(QMainWindow):
    """主窗口类，负责UI布局和交互逻辑"""

    def __init__(self):
        super().__init__()
        self.image_processor = ImageProcessor()
        self.video_processor = None
        self.video_file_processor = None
        self.cropping = False
        self.crop_start = QPoint()
        self.crop_end = QPoint()
        self.video_file_path = None

        self.init_ui()

    def init_ui(self):
        """初始化UI界面"""
        self.setWindowTitle("图像处理与计算机视觉平台")
        self.setGeometry(100, 100, 1200, 800)

        # 创建中央部件和布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QHBoxLayout(central_widget)

        # 左侧面板 - 工具栏和参数设置
        left_panel = QWidget()
        left_panel.setMaximumWidth(300)
        left_layout = QVBoxLayout(left_panel)
        main_layout.addWidget(left_panel)

        # 工具栏
        tool_tabs = QTabWidget()
        left_layout.addWidget(tool_tabs)

        # 文件操作选项卡
        file_tab = QWidget()
        file_layout = QVBoxLayout(file_tab)

        load_btn = QPushButton("加载图像")
        load_btn.clicked.connect(self.load_image)
        file_layout.addWidget(load_btn)

        load_video_btn = QPushButton("加载视频")
        load_video_btn.clicked.connect(self.load_video)
        file_layout.addWidget(load_video_btn)

        save_btn = QPushButton("保存图像")
        save_btn.clicked.connect(self.save_image)
        file_layout.addWidget(save_btn)

        undo_btn = QPushButton("撤销")
        undo_btn.clicked.connect(self.undo)
        file_layout.addWidget(undo_btn)

        redo_btn = QPushButton("重做")
        redo_btn.clicked.connect(self.redo)
        file_layout.addWidget(redo_btn)

        tool_tabs.addTab(file_tab, "文件")

        # 图像处理选项卡
        process_tab = QWidget()
        process_layout = QVBoxLayout(process_tab)

        # 旋转组
        rotate_group = QGroupBox("旋转")
        rotate_layout = QVBoxLayout(rotate_group)

        rotate_angle = QSpinBox()
        rotate_angle.setRange(-180, 180)
        rotate_angle.setValue(0)
        rotate_angle.setSuffix("°")

        rotate_btn = QPushButton("旋转")
        rotate_btn.clicked.connect(lambda: self.rotate(rotate_angle.value()))

        rotate_layout.addWidget(QLabel("旋转角度:"))
        rotate_layout.addWidget(rotate_angle)
        rotate_layout.addWidget(rotate_btn)

        # 缩放组
        resize_group = QGroupBox("缩放")
        resize_layout = QVBoxLayout(resize_group)

        resize_scale = QDoubleSpinBox()
        resize_scale.setRange(0.1, 5.0)
        resize_scale.setValue(1.0)
        resize_scale.setSingleStep(0.1)
        resize_scale.setSuffix("x")

        resize_btn = QPushButton("缩放")
        resize_btn.clicked.connect(lambda: self.resize(resize_scale.value()))

        resize_layout.addWidget(QLabel("缩放比例:"))
        resize_layout.addWidget(resize_scale)
        resize_layout.addWidget(resize_btn)

        # 裁剪组
        crop_group = QGroupBox("裁剪")
        crop_layout = QVBoxLayout(crop_group)

        crop_btn = QPushButton("开始裁剪")
        crop_btn.clicked.connect(self.start_crop)

        confirm_crop_btn = QPushButton("确认裁剪")
        confirm_crop_btn.clicked.connect(self.confirm_crop)
        confirm_crop_btn.setEnabled(False)
        self.confirm_crop_btn = confirm_crop_btn

        crop_layout.addWidget(crop_btn)
        crop_layout.addWidget(confirm_crop_btn)

        process_layout.addWidget(rotate_group)
        process_layout.addWidget(resize_group)
        process_layout.addWidget(crop_group)
        tool_tabs.addTab(process_tab, "基础编辑")

        # 色彩调整选项卡
        color_tab = QWidget()
        color_layout = QVBoxLayout(color_tab)

        # 亮度调整
        brightness_group = QGroupBox("亮度")
        brightness_layout = QVBoxLayout(brightness_group)

        brightness_slider = QSlider(Qt.Horizontal)
        brightness_slider.setRange(-100, 100)
        brightness_slider.setValue(0)

        brightness_value = QLabel("0")

        brightness_apply = QPushButton("应用")
        brightness_apply.clicked.connect(lambda: self.adjust_brightness(brightness_slider.value()))

        brightness_slider.valueChanged.connect(lambda value: brightness_value.setText(str(value)))

        brightness_layout.addWidget(QLabel("亮度调整:"))
        brightness_layout.addWidget(brightness_slider)
        brightness_layout.addWidget(brightness_value)
        brightness_layout.addWidget(brightness_apply)

        # 对比度调整
        contrast_group = QGroupBox("对比度")
        contrast_layout = QVBoxLayout(contrast_group)

        contrast_slider = QSlider(Qt.Horizontal)
        contrast_slider.setRange(-100, 100)
        contrast_slider.setValue(0)

        contrast_value = QLabel("0")

        contrast_apply = QPushButton("应用")
        contrast_apply.clicked.connect(lambda: self.adjust_contrast(contrast_slider.value()))

        contrast_slider.valueChanged.connect(lambda value: contrast_value.setText(str(value)))

        contrast_layout.addWidget(QLabel("对比度调整:"))
        contrast_layout.addWidget(contrast_slider)
        contrast_layout.addWidget(contrast_value)
        contrast_layout.addWidget(contrast_apply)

        # 饱和度调整
        saturation_group = QGroupBox("饱和度")
        saturation_layout = QVBoxLayout(saturation_group)

        saturation_slider = QSlider(Qt.Horizontal)
        saturation_slider.setRange(-100, 100)
        saturation_slider.setValue(0)

        saturation_value = QLabel("0")

        saturation_apply = QPushButton("应用")
        saturation_apply.clicked.connect(lambda: self.adjust_saturation(saturation_slider.value()))

        saturation_slider.valueChanged.connect(lambda value: saturation_value.setText(str(value)))

        saturation_layout.addWidget(QLabel("饱和度调整:"))
        saturation_layout.addWidget(saturation_slider)
        saturation_layout.addWidget(saturation_value)
        saturation_layout.addWidget(saturation_apply)

        color_layout.addWidget(brightness_group)
        color_layout.addWidget(contrast_group)
        color_layout.addWidget(saturation_group)
        tool_tabs.addTab(color_tab, "色彩调整")

        # 计算机视觉选项卡
        cv_tab = QWidget()
        cv_layout = QVBoxLayout(cv_tab)

        # 形状识别
        shape_btn = QPushButton("形状识别")
        shape_btn.clicked.connect(self.detect_shapes)
        cv_layout.addWidget(shape_btn)

        # 人脸识别
        face_btn = QPushButton("人脸检测")
        face_btn.clicked.connect(self.detect_faces)
        cv_layout.addWidget(face_btn)

        # 图像拼接
        stitch_btn = QPushButton("图像拼接")
        stitch_btn.clicked.connect(self.stitch_images)
        cv_layout.addWidget(stitch_btn)

        # 视频处理
        video_group = QGroupBox("视频处理")
        video_layout = QVBoxLayout(video_group)

        start_video_btn = QPushButton("开始摄像头")
        start_video_btn.clicked.connect(self.start_video)
        video_layout.addWidget(start_video_btn)

        stop_video_btn = QPushButton("停止摄像头")
        stop_video_btn.clicked.connect(self.stop_video)
        stop_video_btn.setEnabled(False)
        self.stop_video_btn = stop_video_btn
        video_layout.addWidget(stop_video_btn)

        face_detection_video = QRadioButton("人脸检测")
        shape_detection_video = QRadioButton("形状识别")
        no_processing_video = QRadioButton("无处理")
        no_processing_video.setChecked(True)

        self.video_processing_method = "none"
        video_method_group = QButtonGroup()
        video_method_group.addButton(face_detection_video)
        video_method_group.addButton(shape_detection_video)
        video_method_group.addButton(no_processing_video)

        face_detection_video.toggled.connect(
            lambda: self.set_video_processing("face") if face_detection_video.isChecked() else None)
        shape_detection_video.toggled.connect(
            lambda: self.set_video_processing("shape") if shape_detection_video.isChecked() else None)
        no_processing_video.toggled.connect(
            lambda: self.set_video_processing("none") if no_processing_video.isChecked() else None)

        video_layout.addWidget(QLabel("实时处理方法:"))
        video_layout.addWidget(face_detection_video)
        video_layout.addWidget(shape_detection_video)
        video_layout.addWidget(no_processing_video)

        cv_layout.addWidget(video_group)

        tool_tabs.addTab(cv_tab, "计算机视觉")

        # 平滑处理选项卡
        smooth_tab = QWidget()
        smooth_layout = QVBoxLayout(smooth_tab)

        smooth_method = QComboBox()
        smooth_method.addItems(["均值滤波", "高斯滤波", "中值滤波"])

        smooth_kernel = QSpinBox()
        smooth_kernel.setRange(1, 15)
        smooth_kernel.setValue(3)
        smooth_kernel.setSingleStep(2)

        smooth_btn = QPushButton("应用平滑")
        smooth_btn.clicked.connect(lambda: self.smooth(smooth_method.currentText(), smooth_kernel.value()))

        smooth_layout.addWidget(QLabel("平滑方法:"))
        smooth_layout.addWidget(smooth_method)
        smooth_layout.addWidget(QLabel("核大小 (奇数):"))
        smooth_layout.addWidget(smooth_kernel)
        smooth_layout.addWidget(smooth_btn)

        tool_tabs.addTab(smooth_tab, "平滑处理")

        # 右侧面板 - 图像/视频显示区域
        right_panel = QWidget()
        right_layout = QVBoxLayout(right_panel)
        main_layout.addWidget(right_panel, 1)

        # 图像/视频显示区域
        self.image_label = QLabel("请加载图像或视频")
        self.image_label.setAlignment(Qt.AlignCenter)
        self.image_label.setMinimumSize(400, 400)
        self.image_label.setStyleSheet("border: 1px solid #cccccc;")
        self.image_label.mousePressEvent = self.mouse_press_event
        self.image_label.mouseMoveEvent = self.mouse_move_event
        self.image_label.mouseReleaseEvent = self.mouse_release_event
        right_layout.addWidget(self.image_label, 1)

        # 视频控制区域
        self.video_control_widget = QWidget()
        self.video_control_widget.hide()  # 默认隐藏，加载视频后显示
        video_control_layout = QHBoxLayout(self.video_control_widget)

        self.play_btn = QPushButton("播放")
        self.play_btn.clicked.connect(self.play_video)

        self.pause_btn = QPushButton("暂停")
        self.pause_btn.clicked.connect(self.pause_video)
        self.pause_btn.setEnabled(False)

        self.stop_btn = QPushButton("停止")
        self.stop_btn.clicked.connect(self.stop_video_file)
        self.stop_btn.setEnabled(False)

        self.rewind_btn = QPushButton("快退")
        self.rewind_btn.clicked.connect(self.rewind_video)

        self.forward_btn = QPushButton("快进")
        self.forward_btn.clicked.connect(self.forward_video)

        self.time_label = QLabel("00:00/00:00")

        self.video_slider = QSlider(Qt.Horizontal)
        self.video_slider.sliderMoved.connect(self.seek_video)

        video_control_layout.addWidget(self.play_btn)
        video_control_layout.addWidget(self.pause_btn)
        video_control_layout.addWidget(self.stop_btn)
        video_control_layout.addWidget(self.rewind_btn)
        video_control_layout.addWidget(self.forward_btn)
        video_control_layout.addWidget(self.video_slider)
        video_control_layout.addWidget(self.time_label)

        right_layout.addWidget(self.video_control_widget)

        # 状态栏
        self.statusBar().showMessage("就绪")

        # 显示窗口
        self.show()

    def load_image(self):
        """加载图像"""
        self.stop_all_video_processing()

        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图像文件", "", "图像文件 (*.png *.jpg *.jpeg *.bmp *.webp)"
        )

        if file_path:
            try:
                self.statusBar().showMessage(f"加载图像: {os.path.basename(file_path)}")
                img = self.image_processor.load_image(file_path)
                self.display_image(img)
                self.video_control_widget.hide()  # 隐藏视频控制
                self.statusBar().showMessage(f"加载成功: {os.path.basename(file_path)}")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"加载图像失败: {str(e)}")
                self.statusBar().showMessage("加载图像失败")

    def load_video(self):
        """加载视频文件"""
        self.stop_all_video_processing()

        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择视频文件", "", "视频文件 (*.mp4 *.avi *.mov *.mkv)"
        )

        if file_path:
            try:
                self.video_file_path = file_path
                self.statusBar().showMessage(f"加载视频: {os.path.basename(file_path)}")

                # 显示视频第一帧
                cap = cv2.VideoCapture(file_path)
                if cap.isOpened():
                    ret, frame = cap.read()
                    if ret:
                        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                        self.display_image(frame)
                        self.video_control_widget.show()  # 显示视频控制
                        self.statusBar().showMessage(f"视频加载成功: {os.path.basename(file_path)}")
                    else:
                        raise ValueError("无法读取视频帧")
                    cap.release()
                else:
                    raise ValueError("无法打开视频文件")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"加载视频失败: {str(e)}")
                self.statusBar().showMessage("加载视频失败")

    def save_image(self):
        """保存图像"""
        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "没有可保存的图像")
            return

        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存图像", "", "PNG文件 (*.png);;JPEG文件 (*.jpg);;BMP文件 (*.bmp)"
        )

        if file_path:
            try:
                img = cv2.cvtColor(self.image_processor.current_img, cv2.COLOR_RGB2BGR)
                cv2.imwrite(file_path, img)
                self.statusBar().showMessage(f"保存成功: {os.path.basename(file_path)}")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"保存图像失败: {str(e)}")
                self.statusBar().showMessage("保存图像失败")

    def display_image(self, img):
        """显示图像"""
        if img is None:
            return

        height, width, channel = img.shape
        bytes_per_line = channel * width
        q_img = QImage(img.data, width, height, bytes_per_line, QImage.Format_RGB888)
        self.image_label.setPixmap(QPixmap.fromImage(q_img))

    def stop_all_video_processing(self):
        """停止所有视频处理"""
        if self.video_processor and self.video_processor.isRunning():
            self.video_processor.stop()

        if self.video_file_processor and self.video_file_processor.isRunning():
            self.video_file_processor.stop()

    def undo(self):
        """撤销操作"""
        self.stop_all_video_processing()

        img = self.image_processor.undo()
        if img is not None:
            self.display_image(img)
            self.statusBar().showMessage("撤销成功")

    def redo(self):
        """重做操作"""
        self.stop_all_video_processing()

        img = self.image_processor.redo()
        if img is not None:
            self.display_image(img)
            self.statusBar().showMessage("重做成功")

    def rotate(self, angle):
        """旋转图像"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.rotate(angle)
        self.display_image(img)
        self.statusBar().showMessage(f"旋转 {angle} 度")

    def resize(self, scale):
        """缩放图像"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.resize(scale)
        self.display_image(img)
        self.statusBar().showMessage(f"缩放至 {scale * 100}%")

    def start_crop(self):
        """开始裁剪"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        self.cropping = True
        self.confirm_crop_btn.setEnabled(True)
        self.statusBar().showMessage("请在图像上拖动鼠标选择裁剪区域")

    def confirm_crop(self):
        """确认裁剪"""
        if not self.cropping:
            return

        self.cropping = False
        self.confirm_crop_btn.setEnabled(False)

        if self.crop_start == self.crop_end:
            self.statusBar().showMessage("裁剪区域为空，取消裁剪")
            return

        # 确保坐标正确
        x1 = min(self.crop_start.x(), self.crop_end.x())
        y1 = min(self.crop_start.y(), self.crop_end.y())
        x2 = max(self.crop_start.x(), self.crop_end.x())
        y2 = max(self.crop_start.y(), self.crop_end.y())

        # 将坐标转换为图像坐标系
        pixmap = self.image_label.pixmap()
        if pixmap is None:
            return

        img_width = pixmap.width()
        img_height = pixmap.height()
        label_width = self.image_label.width()
        label_height = self.image_label.height()

        # 计算缩放比例
        scale_x = img_width / label_width
        scale_y = img_height / label_height

        # 转换坐标
        x1_img = int(x1 * scale_x)
        y1_img = int(y1 * scale_y)
        x2_img = int(x2 * scale_x)
        y2_img = int(y2 * scale_y)

        # 确保坐标在有效范围内
        x1_img = max(0, min(x1_img, img_width - 1))
        y1_img = max(0, min(y1_img, img_height - 1))
        x2_img = max(0, min(x2_img, img_width - 1))
        y2_img = max(0, min(y2_img, img_height - 1))

        # 裁剪图像
        try:
            img = self.image_processor.crop(x1_img, y1_img, x2_img, y2_img)
            self.display_image(img)
            self.statusBar().showMessage("裁剪成功")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"裁剪失败: {str(e)}")
            self.statusBar().showMessage("裁剪失败")

    def mouse_press_event(self, event):
        """鼠标按下事件"""
        if self.cropping and event.button() == Qt.LeftButton:
            self.crop_start = event.pos()
            self.crop_end = event.pos()
            self.update_crop_rect()

    def mouse_move_event(self, event):
        """鼠标移动事件"""
        if self.cropping and (event.buttons() & Qt.LeftButton):
            self.crop_end = event.pos()
            self.update_crop_rect()

    def mouse_release_event(self, event):
        """鼠标释放事件"""
        if self.cropping and event.button() == Qt.LeftButton:
            self.crop_end = event.pos()
            self.update_crop_rect()

    def update_crop_rect(self):
        """更新裁剪矩形显示"""
        if not self.cropping:
            return

        # 获取当前显示的图像
        pixmap = self.image_label.pixmap()
        if pixmap is None:
            return

        # 创建绘图对象
        painter = QPainter(pixmap)
        painter.setPen(QPen(QColor(255, 0, 0), 2, Qt.DashLine))

        # 绘制矩形
        x = min(self.crop_start.x(), self.crop_end.x())
        y = min(self.crop_start.y(), self.crop_end.y())
        w = abs(self.crop_start.x() - self.crop_end.x())
        h = abs(self.crop_start.y() - self.crop_end.y())
        painter.drawRect(x, y, w, h)

        painter.end()

        # 更新显示
        self.image_label.setPixmap(pixmap)

    def adjust_brightness(self, value):
        """调整亮度"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.adjust_brightness(value)
        self.display_image(img)
        self.statusBar().showMessage(f"亮度调整: {value}")

    def adjust_contrast(self, value):
        """调整对比度"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.adjust_contrast(value)
        self.display_image(img)
        self.statusBar().showMessage(f"对比度调整: {value}")

    def adjust_saturation(self, value):
        """调整饱和度"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.adjust_saturation(value)
        self.display_image(img)
        self.statusBar().showMessage(f"饱和度调整: {value}")

    def smooth(self, method, kernel_size):
        """应用平滑处理"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.smooth(method, kernel_size)
        self.display_image(img)
        self.statusBar().showMessage(f"应用{method}，核大小: {kernel_size}")

    def detect_shapes(self):
        """检测图像中的形状"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.detect_shapes()
        self.display_image(img)
        self.statusBar().showMessage("形状识别完成")

    def detect_faces(self):
        """检测图像中的人脸"""
        self.stop_all_video_processing()

        if self.image_processor.current_img is None:
            QMessageBox.warning(self, "警告", "请先加载图像")
            return

        img = self.image_processor.detect_faces()
        self.display_image(img)
        self.statusBar().showMessage("人脸检测完成")

    def stitch_images(self):
        """拼接多个图像"""
        self.stop_all_video_processing()

        file_paths, _ = QFileDialog.getOpenFileNames(
            self, "选择要拼接的图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)"
        )

        if len(file_paths) < 2:
            QMessageBox.warning(self, "警告", "请选择至少两个图像进行拼接")
            return

        try:
            images = []
            for path in file_paths:
                img = cv2.imread(path)
                if img is None:
                    raise ValueError(f"无法加载图像: {path}")
                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                images.append(img)

            result = self.image_processor.stitch_images(images)
            self.display_image(result)
            self.statusBar().showMessage(f"成功拼接 {len(images)} 张图像")
        except Exception as e:
            QMessageBox.critical(self, "错误", f"图像拼接失败: {str(e)}")
            self.statusBar().showMessage("图像拼接失败")

    def start_video(self):
        """开始摄像头"""
        self.stop_all_video_processing()

        self.video_control_widget.hide()  # 隐藏视频控制

        if self.video_processing_method == "face":
            processing_function = self.image_processor.detect_faces
        elif self.video_processing_method == "shape":
            processing_function = self.image_processor.detect_shapes
        else:
            processing_function = None

        self.video_processor = VideoProcessor(processing_function=processing_function)
        self.video_processor.frame_updated.connect(self.display_image)
        self.video_processor.process_finished.connect(self.video_processing_finished)
        self.video_processor.start()

        self.stop_video_btn.setEnabled(True)
        self.statusBar().showMessage("摄像头已启动")

    def stop_video(self):
        """停止摄像头"""
        if self.video_processor and self.video_processor.isRunning():
            self.video_processor.stop()
            self.statusBar().showMessage("摄像头已停止")

    def video_processing_finished(self):
        """视频处理完成回调"""
        self.stop_video_btn.setEnabled(False)
        self.statusBar().showMessage("视频处理已停止")

    def set_video_processing(self, method):
        """设置视频处理方法"""
        self.video_processing_method = method
        self.statusBar().showMessage(f"视频处理方法已设置为: {method}")

    def play_video(self):
        """播放视频"""
        if not self.video_file_path:
            QMessageBox.warning(self, "警告", "请先加载视频")
            return

        if not self.video_file_processor or not self.video_file_processor.isRunning():
            if self.video_processing_method == "face":
                processing_function = lambda frame: self.image_processor.detect_faces()
            elif self.video_processing_method == "shape":
                processing_function = lambda frame: self.image_processor.detect_shapes()
            else:
                processing_function = None

            self.video_file_processor = VideoFileProcessor(
                file_path=self.video_file_path,
                processing_function=processing_function
            )
            self.video_file_processor.frame_updated.connect(self.update_video_frame)
            self.video_file_processor.process_finished.connect(self.video_playback_finished)
            self.video_file_processor.start()

        else:
            self.video_file_processor.resume()

        self.play_btn.setEnabled(False)
        self.pause_btn.setEnabled(True)
        self.stop_btn.setEnabled(True)
        self.statusBar().showMessage("播放视频")

    def pause_video(self):
        """暂停视频"""
        if self.video_file_processor and self.video_file_processor.isRunning():
            self.video_file_processor.pause()
            self.play_btn.setEnabled(True)
            self.pause_btn.setEnabled(False)
            self.statusBar().showMessage("视频已暂停")

    def stop_video_file(self):
        """停止视频播放"""
        if self.video_file_processor and self.video_file_processor.isRunning():
            self.video_file_processor.stop()
            self.play_btn.setEnabled(True)
            self.pause_btn.setEnabled(False)
            self.stop_btn.setEnabled(False)
            self.statusBar().showMessage("视频已停止")

    def update_video_frame(self, frame, current_frame, total_frames):
        """更新视频帧"""
        self.display_image(frame)

        # 更新进度条
        self.video_slider.setRange(0, total_frames)
        self.video_slider.setValue(current_frame)

        # 更新时间标签
        current_seconds = current_frame / self.video_file_processor.fps
        total_seconds = total_frames / self.video_file_processor.fps

        current_time = time.strftime('%M:%S', time.gmtime(current_seconds))
        total_time = time.strftime('%M:%S', time.gmtime(total_seconds))

        self.time_label.setText(f"{current_time}/{total_time}")

    def video_playback_finished(self):
        """视频播放完成回调"""
        self.play_btn.setEnabled(True)
        self.pause_btn.setEnabled(False)
        self.stop_btn.setEnabled(False)
        self.statusBar().showMessage("视频播放完毕")

    def rewind_video(self):
        """快退视频"""
        if self.video_file_processor and self.video_file_processor.isRunning():
            current = self.video_slider.value()
            new_pos = max(0, current - int(self.video_file_processor.fps * 5))  # 快退5秒
            self.video_file_processor.seek(new_pos)
            self.statusBar().showMessage("快退5秒")

    def forward_video(self):
        """快进视频"""
        if self.video_file_processor and self.video_file_processor.isRunning():
            current = self.video_slider.value()
            new_pos = min(self.video_slider.maximum(), current + int(self.video_file_processor.fps * 5))  # 快进5秒
            self.video_file_processor.seek(new_pos)
            self.statusBar().showMessage("快进5秒")

    def seek_video(self, position):
        """跳转到指定位置"""
        if self.video_file_processor and self.video_file_processor.isRunning():
            self.video_file_processor.seek(position)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())












# import sys
# import cv2
# import numpy as np
# from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
#                              QHBoxLayout, QLabel, QPushButton, QFileDialog, QSlider,
#                              QSpinBox, QDoubleSpinBox, QComboBox, QGroupBox, QGridLayout,
#                              QMessageBox, QSplitter, QAction, QMenuBar, QToolBar, QStatusBar)
# from PyQt5.QtGui import QImage, QPixmap, QFont, QIcon
# from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
# import os
# from datetime import datetime
# import json
#
#
# class ImageProcessor:
#     def __init__(self):
#         self.original_image = None
#         self.processed_image = None
#         self.file_path = None
#         self.history = []  # 用于撤销/重做操作的历史记录
#         self.history_index = -1  # 当前历史记录位置
#
#     def load_image(self, file_path):
#         self.file_path = file_path
#         self.original_image = cv2.imread(file_path)
#         if self.original_image is None:
#             return False
#
#         # 初始化处理后的图像和历史记录
#         self.processed_image = self.original_image.copy()
#         self.history = [self.processed_image.copy()]
#         self.history_index = 0
#         return True
#
#     def get_current_image(self):
#         return self.processed_image if self.processed_image is not None else self.original_image
#
#     def save_image(self, file_path=None):
#         if file_path:
#             self.file_path = file_path
#         if self.processed_image is not None and self.file_path:
#             cv2.imwrite(self.file_path, self.processed_image)
#             return True
#         return False
#
#     def _save_state(self):
#         """保存当前状态到历史记录"""
#         # 如果不是在历史记录的最新位置，删除后面的记录
#         if self.history_index < len(self.history) - 1:
#             self.history = self.history[:self.history_index + 1]
#
#         self.history.append(self.processed_image.copy())
#         self.history_index += 1
#
#     def undo(self):
#         """撤销操作"""
#         if self.history_index > 0:
#             self.history_index -= 1
#             self.processed_image = self.history[self.history_index].copy()
#             return True
#         return False
#
#     def redo(self):
#         """重做操作"""
#         if self.history_index < len(self.history) - 1:
#             self.history_index += 1
#             self.processed_image = self.history[self.history_index].copy()
#             return True
#         return False
#
#     def rotate(self, angle):
#         if self.processed_image is not None:
#             self._save_state()
#             rows, cols = self.processed_image.shape[:2]
#             M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1)
#             self.processed_image = cv2.warpAffine(self.processed_image, M, (cols, rows))
#             return True
#         return False
#
#     def resize(self, scale):
#         if self.processed_image is not None:
#             self._save_state()
#             width = int(self.processed_image.shape[1] * scale)
#             height = int(self.processed_image.shape[0] * scale)
#             self.processed_image = cv2.resize(self.processed_image, (width, height), interpolation=cv2.INTER_AREA)
#             return True
#         return False
#
#     def crop(self, x1, y1, x2, y2):
#         if self.processed_image is not None:
#             self._save_state()
#             self.processed_image = self.processed_image[y1:y2, x1:x2]
#             return True
#         return False
#
#     def adjust_brightness_contrast(self, brightness=0, contrast=1.0):
#         if self.processed_image is not None:
#             self._save_state()
#             self.processed_image = cv2.convertScaleAbs(self.processed_image, alpha=contrast, beta=brightness)
#             return True
#         return False
#
#     def smooth(self, kernel_size=3):
#         if self.processed_image is not None:
#             self._save_state()
#             self.processed_image = cv2.GaussianBlur(self.processed_image, (kernel_size, kernel_size), 0)
#             return True
#         return False
#
#     def detect_shapes(self):
#         if self.processed_image is not None:
#             self._save_state()
#             img = self.processed_image.copy()
#             gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#             edges = cv2.Canny(gray, 50, 150)
#             contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#
#             for cnt in contours:
#                 area = cv2.contourArea(cnt)
#                 if area > 100:  # 过滤小轮廓
#                     approx = cv2.approxPolyDP(cnt, 0.02 * cv2.arcLength(cnt, True), True)
#                     if len(approx) == 3:
#                         shape = "Triangle"
#                     elif len(approx) == 4:
#                         shape = "Rectangle"
#                     elif len(approx) >= 5:
#                         shape = "Polygon"
#                     else:
#                         shape = "Circle"
#                     cv2.drawContours(img, [cnt], -1, (0, 255, 0), 2)
#                     cv2.putText(img, shape, (approx[0][0][0], approx[0][0][1]),
#                                 cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
#             self.processed_image = img
#             return True
#         return False
#
#     def face_detection(self):
#         if self.processed_image is not None:
#             self._save_state()
#             img = self.processed_image.copy()
#             gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#
#             # 加载人脸检测器
#             face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
#             faces = face_cascade.detectMultiScale(gray, 1.3, 5)
#
#             for (x, y, w, h) in faces:
#                 cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
#                 roi_gray = gray[y:y + h, x:x + w]
#                 roi_color = img[y:y + h, x:x + w]
#
#             self.processed_image = img
#             return len(faces) > 0
#         return False
#
#
# class VideoProcessor(QThread):
#     frame_ready = pyqtSignal(np.ndarray)
#     error_occurred = pyqtSignal(str)
#     finished = pyqtSignal()
#
#     def __init__(self, camera_index=0, file_path=None):
#         super().__init__()
#         self.camera_index = camera_index
#         self.file_path = file_path
#         self.cap = None
#         self.running = False
#         self.paused = False
#         self.processing_function = None
#
#     def run(self):
#         try:
#             if self.file_path:
#                 self.cap = cv2.VideoCapture(self.file_path)
#             else:
#                 self.cap = cv2.VideoCapture(self.camera_index)
#
#             if not self.cap.isOpened():
#                 self.error_occurred.emit("无法打开视频源")
#                 return
#
#             self.running = True
#             while self.running:
#                 if not self.paused:
#                     ret, frame = self.cap.read()
#                     if not ret:
#                         self.error_occurred.emit("无法读取帧")
#                         break
#
#                     if self.processing_function:
#                         frame = self.processing_function(frame)
#
#                     self.frame_ready.emit(frame)
#                 self.msleep(30)  # 控制帧率
#
#         except Exception as e:
#             self.error_occurred.emit(str(e))
#         finally:
#             self.cleanup()
#             self.finished.emit()
#
#     def set_processing_function(self, function):
#         self.processing_function = function
#
#     def pause(self):
#         self.paused = True
#
#     def resume(self):
#         self.paused = False
#
#     def stop(self):
#         self.running = False
#
#     def cleanup(self):
#         if self.cap and self.cap.isOpened():
#             self.cap.release()
#
#
# class ImageDisplayWidget(QWidget):
#     def __init__(self, parent=None):
#         super().__init__(parent)
#         self.initUI()
#
#     def initUI(self):
#         self.main_layout = QVBoxLayout(self)
#
#         self.image_label = QLabel(self)
#         self.image_label.setAlignment(Qt.AlignCenter)
#         self.image_label.setText("请加载图像")
#         self.image_label.setMinimumSize(400, 300)
#         self.image_label.setStyleSheet("border: 1px solid #ccc;")
#
#         self.main_layout.addWidget(self.image_label)
#
#     def display_image(self, image):
#         if image is None:
#             self.image_label.setText("请加载图像")
#             return
#
#         if len(image.shape) == 3:  # 彩色图像
#             height, width, channel = image.shape
#             bytesPerLine = 3 * width
#             qImg = QImage(image.data, width, height, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
#         else:  # 灰度图像
#             height, width = image.shape
#             qImg = QImage(image.data, width, height, QImage.Format_Grayscale8)
#
#         pixmap = QPixmap.fromImage(qImg)
#         self.image_label.setPixmap(pixmap.scaled(
#             self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
#
#     def resizeEvent(self, event):
#         super().resizeEvent(event)
#         if not self.image_label.pixmap() or self.image_label.pixmap().isNull():
#             return
#         self.image_label.setPixmap(self.image_label.pixmap().scaled(
#             self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
#
#
# class ImageProcessingTab(QWidget):
#     def __init__(self, parent=None):
#         super().__init__(parent)
#         self.processor = ImageProcessor()
#         self.initUI()
#
#     def initUI(self):
#         # 创建主布局
#         main_layout = QHBoxLayout(self)
#
#         # 左侧控制面板
#         control_panel = QVBoxLayout()
#
#         # 文件操作
#         file_group = QGroupBox("文件操作")
#         file_layout = QVBoxLayout(file_group)
#
#         self.load_btn = QPushButton("加载图像")
#         self.load_btn.clicked.connect(self.load_image)
#
#         self.save_btn = QPushButton("保存图像")
#         self.save_btn.clicked.connect(self.save_image)
#         self.save_btn.setEnabled(False)
#
#         file_layout.addWidget(self.load_btn)
#         file_layout.addWidget(self.save_btn)
#
#         # 图像处理操作
#         process_group = QGroupBox("图像处理")
#         process_layout = QVBoxLayout(process_group)
#
#         # 旋转控制
#         rotate_layout = QHBoxLayout()
#         self.rotate_label = QLabel("旋转角度:")
#         self.rotate_spin = QSpinBox()
#         self.rotate_spin.setRange(-180, 180)
#         self.rotate_spin.setValue(0)
#         self.rotate_btn = QPushButton("旋转")
#         self.rotate_btn.clicked.connect(self.rotate_image)
#
#         rotate_layout.addWidget(self.rotate_label)
#         rotate_layout.addWidget(self.rotate_spin)
#         rotate_layout.addWidget(self.rotate_btn)
#
#         # 缩放控制
#         resize_layout = QHBoxLayout()
#         self.resize_label = QLabel("缩放比例:")
#         self.resize_spin = QDoubleSpinBox()
#         self.resize_spin.setRange(0.1, 5.0)
#         self.resize_spin.setValue(1.0)
#         self.resize_spin.setSingleStep(0.1)
#         self.resize_btn = QPushButton("缩放")
#         self.resize_btn.clicked.connect(self.resize_image)
#
#         resize_layout.addWidget(self.resize_label)
#         resize_layout.addWidget(self.resize_spin)
#         resize_layout.addWidget(self.resize_btn)
#
#         # 亮度对比度控制
#         brightness_layout = QHBoxLayout()
#         self.brightness_label = QLabel("亮度:")
#         self.brightness_slider = QSlider(Qt.Horizontal)
#         self.brightness_slider.setRange(-100, 100)
#         self.brightness_slider.setValue(0)
#         self.brightness_btn = QPushButton("应用")
#         self.brightness_btn.clicked.connect(self.adjust_brightness_contrast)
#
#         brightness_layout.addWidget(self.brightness_label)
#         brightness_layout.addWidget(self.brightness_slider)
#         brightness_layout.addWidget(self.brightness_btn)
#
#         contrast_layout = QHBoxLayout()
#         self.contrast_label = QLabel("对比度:")
#         self.contrast_slider = QSlider(Qt.Horizontal)
#         self.contrast_slider.setRange(50, 200)
#         self.contrast_slider.setValue(100)
#         self.contrast_btn = QPushButton("应用")
#         self.contrast_btn.clicked.connect(self.adjust_brightness_contrast)
#
#         contrast_layout.addWidget(self.contrast_label)
#         contrast_layout.addWidget(self.contrast_slider)
#         contrast_layout.addWidget(self.contrast_btn)
#
#         # 平滑处理
#         smooth_layout = QHBoxLayout()
#         self.smooth_label = QLabel("平滑核大小:")
#         self.smooth_spin = QSpinBox()
#         self.smooth_spin.setRange(1, 15)
#         self.smooth_spin.setValue(3)
#         self.smooth_spin.setSingleStep(2)  # 必须是奇数
#         self.smooth_btn = QPushButton("平滑")
#         self.smooth_btn.clicked.connect(self.smooth_image)
#
#         smooth_layout.addWidget(self.smooth_label)
#         smooth_layout.addWidget(self.smooth_spin)
#         smooth_layout.addWidget(self.smooth_btn)
#
#         # 形状识别
#         self.shape_btn = QPushButton("形状识别")
#         self.shape_btn.clicked.connect(self.detect_shapes)
#
#         # 人脸识别
#         self.face_btn = QPushButton("人脸识别")
#         self.face_btn.clicked.connect(self.detect_faces)
#
#         # 添加所有处理控件到布局
#         process_layout.addLayout(rotate_layout)
#         process_layout.addLayout(resize_layout)
#         process_layout.addLayout(brightness_layout)
#         process_layout.addLayout(contrast_layout)
#         process_layout.addLayout(smooth_layout)
#         process_layout.addWidget(self.shape_btn)
#         process_layout.addWidget(self.face_btn)
#
#         # 撤销/重做操作
#         undo_redo_group = QGroupBox("撤销/重做")
#         undo_redo_layout = QHBoxLayout(undo_redo_group)
#
#         self.undo_btn = QPushButton("撤销")
#         self.undo_btn.setEnabled(False)
#         self.undo_btn.clicked.connect(self.undo)
#
#         self.redo_btn = QPushButton("重做")
#         self.redo_btn.setEnabled(False)
#         self.redo_btn.clicked.connect(self.redo)
#
#         undo_redo_layout.addWidget(self.undo_btn)
#         undo_redo_layout.addWidget(self.redo_btn)
#
#         # 添加所有组到控制面板
#         control_panel.addWidget(file_group)
#         control_panel.addWidget(process_group)
#         control_panel.addWidget(undo_redo_group)
#         control_panel.addStretch(1)
#
#         # 右侧图像显示区域
#         self.display_widget = ImageDisplayWidget()
#
#         # 添加到主布局
#         main_layout.addLayout(control_panel, 1)
#         main_layout.addWidget(self.display_widget, 3)
#
#     def load_image(self):
#         file_path, _ = QFileDialog.getOpenFileName(
#             self, "选择图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)")
#         if file_path:
#             if self.processor.load_image(file_path):
#                 self.display_widget.display_image(self.processor.get_current_image())
#                 self.save_btn.setEnabled(True)
#                 self.undo_btn.setEnabled(False)  # 初始状态没有可撤销操作
#                 self.redo_btn.setEnabled(False)  # 初始状态没有可重做操作
#                 self.setWindowTitle(f"计算机视觉开发平台 - {os.path.basename(file_path)}")
#             else:
#                 QMessageBox.warning(self, "错误", "无法加载图像")
#
#     def save_image(self):
#         file_path, _ = QFileDialog.getSaveFileName(
#             self, "保存图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp)")
#         if file_path:
#             if self.processor.save_image(file_path):
#                 QMessageBox.information(self, "成功", "图像保存成功")
#             else:
#                 QMessageBox.warning(self, "错误", "无法保存图像")
#
#     def rotate_image(self):
#         angle = self.rotate_spin.value()
#         if self.processor.rotate(angle):
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#
#     def resize_image(self):
#         scale = self.resize_spin.value()
#         if self.processor.resize(scale):
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#
#     def adjust_brightness_contrast(self):
#         brightness = self.brightness_slider.value()
#         contrast = self.contrast_slider.value() / 100.0
#         if self.processor.adjust_brightness_contrast(brightness, contrast):
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#
#     def smooth_image(self):
#         kernel_size = self.smooth_spin.value()
#         if self.processor.smooth(kernel_size):
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#
#     def detect_shapes(self):
#         if self.processor.detect_shapes():
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#             QMessageBox.information(self, "完成", "形状识别完成")
#
#     def detect_faces(self):
#         if self.processor.face_detection():
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#             QMessageBox.information(self, "完成", "人脸识别完成")
#
#     def undo(self):
#         if self.processor.undo():
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#
#     def redo(self):
#         if self.processor.redo():
#             self.display_widget.display_image(self.processor.get_current_image())
#             self.update_undo_redo_buttons()
#
#     def update_undo_redo_buttons(self):
#         """更新撤销/重做按钮状态"""
#         self.undo_btn.setEnabled(self.processor.history_index > 0)
#         self.redo_btn.setEnabled(self.processor.history_index < len(self.processor.history) - 1)
#
#
# class VideoProcessingTab(QWidget):
#     def __init__(self, parent=None):
#         super().__init__(parent)
#         self.processor = None
#         self.is_playing = False
#         self.initUI()
#
#     def initUI(self):
#         # 创建主布局
#         main_layout = QVBoxLayout(self)
#
#         # 控制栏
#         control_layout = QHBoxLayout()
#
#         self.open_file_btn = QPushButton("打开视频文件")
#         self.open_file_btn.clicked.connect(self.open_video_file)
#
#         self.open_camera_btn = QPushButton("打开摄像头")
#         self.open_camera_btn.clicked.connect(self.open_camera)
#
#         self.play_pause_btn = QPushButton("播放")
#         self.play_pause_btn.setEnabled(False)
#         self.play_pause_btn.clicked.connect(self.toggle_play_pause)
#
#         self.stop_btn = QPushButton("停止")
#         self.stop_btn.setEnabled(False)
#         self.stop_btn.clicked.connect(self.stop_video)
#
#         self.face_detection_btn = QPushButton("人脸检测")
#         self.face_detection_btn.setEnabled(False)
#         self.face_detection_btn.clicked.connect(self.toggle_face_detection)
#
#         self.shape_detection_btn = QPushButton("形状检测")
#         self.shape_detection_btn.setEnabled(False)
#         self.shape_detection_btn.clicked.connect(self.toggle_shape_detection)
#
#         control_layout.addWidget(self.open_file_btn)
#         control_layout.addWidget(self.open_camera_btn)
#         control_layout.addWidget(self.play_pause_btn)
#         control_layout.addWidget(self.stop_btn)
#         control_layout.addWidget(self.face_detection_btn)
#         control_layout.addWidget(self.shape_detection_btn)
#
#         # 视频显示区域
#         self.display_widget = ImageDisplayWidget()
#
#         # 添加到主布局
#         main_layout.addLayout(control_layout)
#         main_layout.addWidget(self.display_widget)
#
#         self.current_processing_function = None
#         self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
#
#     def open_video_file(self):
#         file_path, _ = QFileDialog.getOpenFileName(
#             self, "选择视频文件", "", "视频文件 (*.mp4 *.avi *.mov *.mkv)")
#         if file_path:
#             self.start_video_processor(file_path=file_path)
#
#     def open_camera(self):
#         self.start_video_processor(camera_index=0)
#
#     def start_video_processor(self, camera_index=None, file_path=None):
#         self.stop_video()
#
#         self.processor = VideoProcessor(camera_index, file_path)
#         self.processor.frame_ready.connect(self.display_widget.display_image)
#         self.processor.error_occurred.connect(self.handle_error)
#         self.processor.finished.connect(self.video_finished)
#
#         self.play_pause_btn.setEnabled(True)
#         self.stop_btn.setEnabled(True)
#         self.face_detection_btn.setEnabled(True)
#         self.shape_detection_btn.setEnabled(True)
#
#         self.is_playing = True
#         self.play_pause_btn.setText("暂停")
#         self.processor.start()
#
#     def toggle_play_pause(self):
#         if self.processor:
#             if self.is_playing:
#                 self.processor.pause()
#                 self.play_pause_btn.setText("播放")
#             else:
#                 self.processor.resume()
#                 self.play_pause_btn.setText("暂停")
#             self.is_playing = not self.is_playing
#
#     def stop_video(self):
#         if self.processor:
#             self.processor.stop()
#             self.processor.wait()
#             self.processor = None
#             self.display_widget.display_image(None)
#
#         self.is_playing = False
#         self.play_pause_btn.setText("播放")
#         self.play_pause_btn.setEnabled(False)
#         self.stop_btn.setEnabled(False)
#         self.face_detection_btn.setEnabled(False)
#         self.shape_detection_btn.setEnabled(False)
#
#     def toggle_face_detection(self):
#         if self.processor:
#             if self.current_processing_function == self.detect_faces_in_frame:
#                 self.current_processing_function = None
#                 self.processor.set_processing_function(None)
#                 self.face_detection_btn.setText("人脸检测")
#             else:
#                 self.current_processing_function = self.detect_faces_in_frame
#                 self.processor.set_processing_function(self.current_processing_function)
#                 self.face_detection_btn.setText("关闭人脸检测")
#
#     def toggle_shape_detection(self):
#         if self.processor:
#             if self.current_processing_function == self.detect_shapes_in_frame:
#                 self.current_processing_function = None
#                 self.processor.set_processing_function(None)
#                 self.shape_detection_btn.setText("形状检测")
#             else:
#                 self.current_processing_function = self.detect_shapes_in_frame
#                 self.processor.set_processing_function(self.current_processing_function)
#                 self.shape_detection_btn.setText("关闭形状检测")
#
#     def detect_faces_in_frame(self, frame):
#         gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#         faces = self.face_cascade.detectMultiScale(gray, 1.3, 5)
#
#         for (x, y, w, h) in faces:
#             cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
#
#         return frame
#
#     def detect_shapes_in_frame(self, frame):
#         gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#         edges = cv2.Canny(gray, 50, 150)
#         contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#
#         for cnt in contours:
#             area = cv2.contourArea(cnt)
#             if area > 100:  # 过滤小轮廓
#                 approx = cv2.approxPolyDP(cnt, 0.02 * cv2.arcLength(cnt, True), True)
#                 if len(approx) == 3:
#                     shape = "Triangle"
#                 elif len(approx) == 4:
#                     shape = "Rectangle"
#                 elif len(approx) >= 5:
#                     shape = "Polygon"
#                 else:
#                     shape = "Circle"
#                 cv2.drawContours(frame, [cnt], -1, (0, 255, 0), 2)
#                 cv2.putText(frame, shape, (approx[0][0][0], approx[0][0][1]),
#                             cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
#
#         return frame
#
#     def handle_error(self, error_msg):
#         QMessageBox.warning(self, "错误", error_msg)
#         self.stop_video()
#
#     def video_finished(self):
#         self.is_playing = False
#         self.play_pause_btn.setText("播放")
#
#
# class MainWindow(QMainWindow):
#     def __init__(self):
#         super().__init__()
#         self.initUI()
#
#     def initUI(self):
#         # 设置窗口标题和大小
#         self.setWindowTitle("计算机视觉开发平台")
#         self.resize(1000, 700)
#
#         # 创建标签页（先于菜单和工具栏）
#         self.tabs = QTabWidget()
#
#         # 添加图像处理标签页
#         self.image_tab = ImageProcessingTab()
#         self.tabs.addTab(self.image_tab, "图像处理")
#
#         # 添加视频处理标签页
#         self.video_tab = VideoProcessingTab()
#         self.tabs.addTab(self.video_tab, "视频处理")
#
#         # 创建菜单栏
#         self.create_menu_bar()
#
#         # 创建工具栏
#         self.create_tool_bar()
#
#         # 创建状态栏
#         self.statusBar().showMessage("就绪")
#
#         # 设置中心部件
#         self.setCentralWidget(self.tabs)
#
#     def create_menu_bar(self):
#         menubar = self.menuBar()
#
#         # 文件菜单
#         file_menu = menubar.addMenu("文件")
#
#         # 加载图像动作
#         load_image_action = QAction("加载图像", self)
#         load_image_action.setShortcut("Ctrl+I")
#         load_image_action.triggered.connect(self.image_tab.load_image)
#         file_menu.addAction(load_image_action)
#
#         # 保存图像动作
#         save_image_action = QAction("保存图像", self)
#         save_image_action.setShortcut("Ctrl+S")
#         save_image_action.triggered.connect(self.image_tab.save_image)
#         file_menu.addAction(save_image_action)
#
#         file_menu.addSeparator()
#
#         # 退出动作
#         exit_action = QAction("退出", self)
#         exit_action.setShortcut("Ctrl+Q")
#         exit_action.triggered.connect(self.close)
#         file_menu.addAction(exit_action)
#
#         # 编辑菜单
#         edit_menu = menubar.addMenu("编辑")
#
#         # 撤销动作
#         undo_action = QAction("撤销", self)
#         undo_action.setShortcut("Ctrl+Z")
#         undo_action.triggered.connect(self.image_tab.undo)
#         edit_menu.addAction(undo_action)
#
#         # 重做动作
#         redo_action = QAction("重做", self)
#         redo_action.setShortcut("Ctrl+Y")
#         redo_action.triggered.connect(self.image_tab.redo)
#         edit_menu.addAction(redo_action)
#
#         # 帮助菜单
#         help_menu = menubar.addMenu("帮助")
#
#         # 关于动作
#         about_action = QAction("关于", self)
#         about_action.triggered.connect(self.show_about_dialog)
#         help_menu.addAction(about_action)
#
#     def create_tool_bar(self):
#         toolbar = QToolBar("工具栏")
#         self.addToolBar(toolbar)
#
#         # 加载图像按钮
#         load_image_btn = QAction("加载图像", self)
#         load_image_btn.triggered.connect(self.image_tab.load_image)
#         toolbar.addAction(load_image_btn)
#
#         # 保存图像按钮
#         save_image_btn = QAction("保存图像", self)
#         save_image_btn.triggered.connect(self.image_tab.save_image)
#         toolbar.addAction(save_image_btn)
#
#         toolbar.addSeparator()
#
#         # 撤销按钮
#         undo_btn = QAction("撤销", self)
#         undo_btn.triggered.connect(self.image_tab.undo)
#         toolbar.addAction(undo_btn)
#
#         # 重做按钮
#         redo_btn = QAction("重做", self)
#         redo_btn.triggered.connect(self.image_tab.redo)
#         toolbar.addAction(redo_btn)
#
#     def show_about_dialog(self):
#         QMessageBox.about(self, "关于",
#                           "计算机视觉开发平台\n\n"
#                           "版本: 1.0\n"
#                           "作者: doubao\n"
#                           "日期: 2025年\n\n"
#                           "本平台用于演示基础计算机视觉功能，包括图像处理和视频分析。")
#
#
# if __name__ == "__main__":
#     app = QApplication(sys.argv)
#     # 设置中文字体支持
#     font = QFont("SimHei")
#     app.setFont(font)
#
#     window = MainWindow()
#     window.show()
#     sys.exit(app.exec_())